﻿
module internal Octopus.OctopusService.Functions

open System
open System.IO
open Octopus.OctopusLib
open FunctionsCore



// the templates path DOES NOT END with a backslash '\\'
(*
let private vmTemplatesPath =
  let path = Database.serviceBinPath + @"\serviceConf.xml"
  let xml = new System.Xml.XmlDocument()
  xml.Load(path)
  let templatePath = xml.SelectNodes("/OctopusConf/VirtualMachinesTemplate/Path")
  let s = templatePath.[0].InnerText
  if s.EndsWith("\\") then s.Substring(0, s.LastIndexOf('\\'))  // removes last \
  else s
*)

//-----------------------------------------------------------------------------
// Service functions:


// execute_remote_Octopus_function
let upgradeNic (sr:StreamReader) (sw:StreamWriter) =
  let octopusVmGuid = new System.Guid( sr.ReadLine() )

  let vm = Database.VirtualMachines.getVmByOctopusGuid octopusVmGuid
  if vm.OctopusState <> OctopusVmState.Configuring then
    failwith "The VM specified is not in Configuring state"

  let rec foo () =
        let vm = Database.VirtualMachines.getVmByOctopusGuid octopusVmGuid
        let mutable hfVm = HyperF.HyperF.get_VM (Some vm.CurrentServer) vm.HyperVGuid
        
        // waits hfVm to be powered off (for 120 seconds)
        let mutable seconds = 0
        while (seconds < 60 && hfVm.State <> HyperF.VMState.Stopped && hfVm.State <> HyperF.VMState.Suspended) do
          seconds <- seconds + 1
          System.Threading.Thread.Sleep(1000)
          hfVm <- HyperF.HyperF.get_VM (Some vm.CurrentServer) vm.HyperVGuid

        // turn off the VM if isn't turned off yet
        if hfVm.State <> HyperF.VMState.Stopped && hfVm.State <> HyperF.VMState.Suspended then
          Database.LogFile.writeInLog (sprintf "-- Configuring: forcing turn-off of VM %s" (string octopusVmGuid))
          HyperF.HyperF.stop_VM hfVm
          System.Threading.Thread.Sleep(5000)
          hfVm <- HyperF.HyperF.get_VM (Some vm.CurrentServer) vm.HyperVGuid

        if hfVm.State <> HyperF.VMState.Stopped && hfVm.State <> HyperF.VMState.Suspended then
          foo()     // retry
        else        
          // removes the legacy NIC and adds a vmbus NIC with the same mac address
          let nic = (HyperF.HyperF.get_VMNic hfVm true false).[0]
          let macAddress = nic.MacAddress
          let switch = HyperF.HyperF.get_VMNicSwitch nic
          HyperF.HyperF.remove_VMNic hfVm nic    
          HyperF.HyperF.add_VMNic hfVm (Some switch) false (Some macAddress)
          HyperF.HyperF.start_VM hfVm

  Async.Start (async { foo() })

  sw.WriteLine(true)


// execute_remote_Octopus_function
(*
let notifyHpcNodeReady (sr:StreamReader) (sw:StreamWriter) =
    let octopusVmGuid = new System.Guid( sr.ReadLine() )
    let token = new System.Guid( sr.ReadLine() )

    let vm = Database.VirtualMachines.getVmByOctopusGuid octopusVmGuid
    let xmlPath = vm.StoredPath + @"\\HpcImportFile.xml"

    let hfVm = HyperF.HyperF.get_VM (Some vm.CurrentServer) vm.HyperVGuid
    let hfNic = (HyperF.HyperF.get_VMNic hfVm true true).[0]
    let macAddress = hfNic.MacAddress

    let template = Database.VMTemplates.getVMTemplate vm.VmTemplateID

    // TODO: Calculate in some (other) way the domain name
    let domainName = getDomainInfo "Name"
    Octopus.RemoteHpcPS.Functions.instanceHpcXmlNodeImport template.HpcScheduler vm.HostName domainName vm.Guid macAddress
    //let shell = new Octopus.HpcPowerShell.HpcPowerShell()
    
    // Once, I received this exception:
    // "This operation has conflicted with another operation running concurrently. Once the operation completes, this operation may be rerun"
    // So, I try some times...
    //Utility.trySomeTimes (10,1000) (fun () -> shell.Import_HpcNodeXml xmlPath) |> ignore

    Utility.trySomeTimes (10,1000) (fun () -> Octopus.RemoteHpcPS.Functions.import_HpcNodeXml template.HpcScheduler vm.HostName) |> ignore
    
    Async.Start(
        async {
            System.Threading.Thread.Sleep(15000)
            let f() =
              let nodeState = Octopus.RemoteHpcPS.Functions.getNodeState template.HpcScheduler vm.HostName
              if nodeState = "Offline" then Octopus.RemoteHpcPS.Functions.setNodeOnline template.HpcScheduler vm.HostName
              else failwith "Node not ready"
            try Utility.trySomeTimes (100,5000) f |> ignore
            with e -> let msg = "in notifyHpcNodeReady: " + e.Message + "\r\n" + e.StackTrace in Database.LogFile.writeInLog msg
        })

    sw.WriteLine(true)
*)

// execute_remote_Octopus_function
let notifyConfigurationFailed (sr:StreamReader) (sw:StreamWriter) =
  let octopusVmGuid = new System.Guid( sr.ReadLine() )
  let messageToOwner = sr.ReadLine().Trim()

  let vm = Database.VirtualMachines.getVmByOctopusGuid octopusVmGuid
  let hfVm = HyperF.HyperF.get_VM (Some vm.CurrentServer) vm.HyperVGuid
  HyperF.HyperF.stop_VM hfVm

  Database.VirtualMachines.updateVirtualMachineOctopusState octopusVmGuid OctopusVmState.CreationFailed
  Database.VirtualMachines.updateVirtualMachineHyperVStates octopusVmGuid (Some HyperVState.Stopped) None None

  if messageToOwner <> "" then
    Database.MessagesToUsers.insertNewMessage vm.OwnerName messageToOwner

  sw.WriteLine(true)


// execute_remote_Octopus_function
let notifyVmConfigured (clientip:System.Net.IPAddress) (sr:StreamReader) (sw:StreamWriter) =
  let octopusVmGuid = new System.Guid( sr.ReadLine() )
  let messageToOwner = sr.ReadLine().Trim()
  
  // Set the normal state and the updated ip addrss
  Database.VirtualMachines.updateVirtualMachineOctopusState octopusVmGuid OctopusVmState.Normal
  Database.VirtualMachines.updateVirtualMachineIpv4Address octopusVmGuid clientip

  // retieve the virtual machine data from the database and HyperF
  let vm = Database.VirtualMachines.getVmByOctopusGuid octopusVmGuid
  let hfVm = HyperF.HyperF.get_VM (Some vm.CurrentServer) vm.HyperVGuid

  // Set the updated mac address
  let hfNic = (HyperF.HyperF.get_VMNic hfVm true true).[0]
  let macAddress = hfNic.MacAddress
  Database.VirtualMachines.updateVirtualMachineMacAddress octopusVmGuid macAddress

  // Disconnect the floppy disk (used for the deployment)
  try HyperF.HyperF.remove_VMFloppyDisk hfVm
  with _ -> ()

  // Remove the floppy disk file    
  let deployDiskPath = vm.StoredPath + @"\DeployDisk.vfd"
  if File.Exists(deployDiskPath) then File.Delete(deployDiskPath)
    
  // Finally, send a message to the owner
  if messageToOwner <> "" then
    Database.MessagesToUsers.insertNewMessage vm.OwnerName messageToOwner

  sw.WriteLine(true)


// execute_remote_Octopus_function
let notifyVmIpAddress (clientip:System.Net.IPAddress) (sr:StreamReader) (sw:StreamWriter) =
  let octopusVmGuid = new System.Guid( sr.ReadLine() )
  
  Database.VirtualMachines.updateVirtualMachineIpv4Address octopusVmGuid clientip
   
  sw.WriteLine(true)



//------------


// execute_remote_Octopus_function_withArrayReturn
let createNewVm (sr:StreamReader) (sw:StreamWriter) =
  let ownerName = sr.ReadLine()
  let templateId = Int32.Parse( sr.ReadLine() )
  let numberOfVm = UInt16.Parse( sr.ReadLine() )
  let cpuCount = UInt16.Parse( sr.ReadLine() )
  let memorySizeMb = Int32.Parse( sr.ReadLine() )
  let hostNameJolly = sr.ReadLine()
  let password = sr.ReadLine()

  let vmTemplate = Database.VMTemplates.getVMTemplate templateId
  
  if cpuCount < vmTemplate.CpuCoresMin || cpuCount > vmTemplate.CpuCoresMax then
    failwithf "Invalid number of cpu. They must be between %d and %d." vmTemplate.CpuCoresMin vmTemplate.CpuCoresMax
  if memorySizeMb < vmTemplate.MemorySizeMbMin || memorySizeMb > vmTemplate.MemorySizeMbMax then
    failwithf "Invalid RAM size. It must be between %d and %d MB." (vmTemplate.MemorySizeMbMin) (vmTemplate.MemorySizeMbMax)
  
  let numVm = uint16 (Octopus.OctopusService.General.parseMultipleHostname hostNameJolly).Length
  if (numVm <> numberOfVm) then
    failwith "Invalid hostname for the number of VM you specified"

  let newGuid = System.Guid.NewGuid()
  let memoryInBytes = (uint64 memorySizeMb) * 1048576UL
  let vmr = new VirtualMachineRequested(newGuid, numVm, ownerName, hostNameJolly, cpuCount, memoryInBytes, templateId, password)
  
  Database.VirtualMachines.insertVirtualMachinesRequested vmr
  Clips.assertEvent_creatingNewVm vmr
  
  sw.WriteLine(true)
  sw.WriteLine(string Guid.Empty)


//----------------------------------------------------------------------------


// execute_remote_Octopus_function
let setVmCheckpoint (sr:StreamReader) (sw:StreamWriter) =
  let vmNum = Int32.Parse( sr.ReadLine() )
  let vmGuids = [| for i = 1 to vmNum do yield new System.Guid( sr.ReadLine() ) |]
  
  let vms = vmGuids |> Array.map (fun x -> Database.VirtualMachines.getVmByOctopusGuid x)
  let gvms = groupVms vms
  
  let ghfvms = getHyperFGrouped gvms

  ghfvms |> Array.concat |> Array.iter (fun (v,_) -> if v.OctopusState <> OctopusVmState.Normal then failwith "All the VMs must be in normal state")
  ghfvms |> Array.concat |> Array.iter (fun (v,_) -> if v.IsCheckpointed then failwith "Error, one of the given VMs is already checkpointed")
  ghfvms |> Array.concat |> Array.iter (fun (_,hv) -> if hv.State <> HyperF.VMState.Running then failwith "All the VMs must be in Running state")
  
  // Pause all the VMs (parallel by Hyper-V host)
  let f (hfvm:HyperF.VirtualMachine) = 
    match hfvm.State with
    | HyperF.VMState.Running -> HyperF.HyperF.pause_VM hfvm
    | HyperF.VMState.Paused
    | HyperF.VMState.Suspended
    | HyperF.VMState.Stopped -> ()
    | x -> failwithf "Unable to rollback the state when %s" (x.ToString())
  let comps = [ for vms in ghfvms -> async { for (_,hv) in vms do f hv } ]
  Async.Parallel comps |> Async.RunSynchronously |> ignore
  
  // Checkpoint all the VMs (parallel by Hyper-V host)
  let comps = [ for vms in ghfvms -> async { for v in vms do set_VMCheckpoint v None } ]
  Async.Parallel comps |> Async.RunSynchronously |> ignore

  // Start & pause all the VMs (parallel by Hyper-V host)
  let comps = [ for vms in ghfvms -> async { for (_,hv) in vms do HyperF.HyperF.start_VM hv; HyperF.HyperF.pause_VM hv } ]
  Async.Parallel comps |> Async.RunSynchronously |> ignore

  // Start all the VMs (parallel by Hyper-V host)
  let comps = [ for vms in ghfvms -> async { for (_,hv) in vms do HyperF.HyperF.start_VM hv } ]
  Async.Parallel comps |> Async.RunSynchronously |> ignore
  
  sw.WriteLine(true)
  

// execute_remote_Octopus_function
let commitVmCheckpoint (sr:StreamReader) (sw:StreamWriter) =
  let vmNum = Int32.Parse( sr.ReadLine() )
  let vmGuids = [| for i = 1 to vmNum do yield new System.Guid( sr.ReadLine() ) |]
  
  let vms = vmGuids |> Array.map (fun x -> Database.VirtualMachines.getVmByOctopusGuid x)
  let gvms = groupVms vms
  
  let ghfvms = getHyperFGrouped gvms

  ghfvms |> Array.concat |> Array.iter (fun (v,_) -> if v.OctopusState <> OctopusVmState.Normal then failwith "All the VMs must be in normal state")
  ghfvms |> Array.concat |> Array.iter (fun (_,hv) -> if hv.State <> HyperF.VMState.Running then failwith "All the VMs must be in Running state")
  
  // Pause all the VMs (parallel by Hyper-V host)
  let f (hfvm:HyperF.VirtualMachine) = 
    match hfvm.State with
    | HyperF.VMState.Running -> HyperF.HyperF.pause_VM hfvm
    | HyperF.VMState.Paused
    | HyperF.VMState.Suspended
    | HyperF.VMState.Stopped -> ()
    | x -> failwithf "Unable to rollback the state when %s" (x.ToString())
  let comps = [ for vms in ghfvms -> async { for (_,hv) in vms do f hv } ]
  Async.Parallel comps |> Async.RunSynchronously |> ignore
  
  // Checkpoint all the VMs (parallel by Hyper-V host)
  let comps = [ for vms in ghfvms -> async { for v in vms do commit_VMCheckpoint v None } ]
  Async.Parallel comps |> Async.RunSynchronously |> ignore

  // Start & pause all the VMs (parallel by Hyper-V host)
  let comps = [ for vms in ghfvms -> async { for (_,hv) in vms do HyperF.HyperF.start_VM hv; HyperF.HyperF.pause_VM hv } ]
  Async.Parallel comps |> Async.RunSynchronously |> ignore

  // Start all the VMs (parallel by Hyper-V host)
  let comps = [ for vms in ghfvms -> async { for (_,hv) in vms do HyperF.HyperF.start_VM hv } ]
  Async.Parallel comps |> Async.RunSynchronously |> ignore
  
  sw.WriteLine(true)


// execute_remote_Octopus_function
let discardVmCheckpoint (sr:StreamReader) (sw:StreamWriter) =
  let vmNum = Int32.Parse( sr.ReadLine() )
  let vmGuids = [| for i = 1 to vmNum do yield new System.Guid( sr.ReadLine() ) |]
  
  let vms = vmGuids |> Array.map (fun x -> Database.VirtualMachines.getVmByOctopusGuid x)
  let gvms = groupVms vms
  
  let ghfvms = getHyperFGrouped gvms

  ghfvms |> Array.concat |> Array.iter (fun (v,_) -> if v.OctopusState <> OctopusVmState.Normal then failwith "All the VMs must be in normal state")
  ghfvms |> Array.concat |> Array.iter (fun (v,_) -> if not(v.IsCheckpointed) then failwith "All the VMs must be checkpointed")
  //ghfvms |> Array.concat |> Array.iter (fun (_,hv) -> if hv.State <> HyperF.VMState.Running then failwith "All the VMs must be in Running state")
  
  // Stop all the VMs (parallel by Hyper-V host)
  let f (hfvm:HyperF.VirtualMachine) = 
    match hfvm.State with
    | HyperF.VMState.Paused
    | HyperF.VMState.Suspended
    | HyperF.VMState.Running -> HyperF.HyperF.stop_VM hfvm
    | HyperF.VMState.Stopped -> ()
    | x -> failwithf "Unable to rollback the state when %s" (x.ToString())
  let comps = [ for vms in ghfvms -> async { for (_,hv) in vms do f hv } ]
  Async.Parallel comps |> Async.RunSynchronously |> ignore
  
  // Delete the checkpoints for all the VMs (parallel by Hyper-V host)
  let comps = [ for vms in ghfvms -> async { for v in vms do discard_VMCheckpoint v None } ]
  Async.Parallel comps |> Async.RunSynchronously |> ignore

  // Start & pause all the VMs (parallel by Hyper-V host)
  let comps = [ for vms in ghfvms -> async { for (_,hv) in vms do HyperF.HyperF.start_VM hv; HyperF.HyperF.pause_VM hv } ]
  Async.Parallel comps |> Async.RunSynchronously |> ignore

  // Start all the VMs (parallel by Hyper-V host)
  let comps = [ for vms in ghfvms -> async { for (_,hv) in vms do HyperF.HyperF.start_VM hv } ]
  Async.Parallel comps |> Async.RunSynchronously |> ignore
  
  sw.WriteLine(true)


// execute_remote_Octopus_function
let stampVm (sr:StreamReader) (sw:StreamWriter) =
  let octopusVmGuid = new System.Guid( sr.ReadLine() )
  
  let vm = Database.VirtualMachines.getVmByOctopusGuid octopusVmGuid
  if vm.HyperVState <> HyperVState.Stopped then
    failwith "Virtual machines need to be stopped to be stamped" 
    
  createTemplateFromVm vm

  sw.WriteLine(true)



//--------------------------------------------------------


// execute_remote_Octopus_function
let migrateVm (sr:StreamReader) (sw:StreamWriter) =
  let octopusVmGuid = new System.Guid( sr.ReadLine() )
  let serverDest = sr.ReadLine()
  
  FunctionsCore.migrateVm octopusVmGuid serverDest
  
  sw.WriteLine(true)


//--------------------------------------------------------


// execute_remote_Octopus_function
let deleteVm (sr:StreamReader) (sw:StreamWriter) =
  let octopusVmGuid = new System.Guid( sr.ReadLine() )

  FunctionsCore.deleteVm octopusVmGuid  

  sw.WriteLine(true)


// execute_remote_Octopus_function
let startVm (sr:StreamReader) (sw:StreamWriter) =
  let octopusVmGuid = new System.Guid( sr.ReadLine() )

  FunctionsCore.startVm octopusVmGuid  
    
  sw.WriteLine(true)


// execute_remote_Octopus_function
let turnoffVm (sr:StreamReader) (sw:StreamWriter) =
  let octopusVmGuid = new System.Guid( sr.ReadLine() )
  
  FunctionsCore.turnoffVm octopusVmGuid

  sw.WriteLine(true)


// execute_remote_Octopus_function
let suspendVm (sr:StreamReader) (sw:StreamWriter) =
  let octopusVmGuid = new System.Guid( sr.ReadLine() )
  
  FunctionsCore.suspendVm octopusVmGuid

  sw.WriteLine(true)


// execute_remote_Octopus_function
let shutdownVm (sr:StreamReader) (sw:StreamWriter) =
  let octopusVmGuid = new System.Guid( sr.ReadLine() )
  let force = Boolean.Parse( sr.ReadLine() )
  let reason = sr.ReadLine()

  FunctionsCore.shutdownVm octopusVmGuid force reason

  sw.WriteLine(true)


// execute_remote_Octopus_function_withArrayReturn
let getAllVm (sr:StreamReader) (sw:StreamWriter) =
  let vms = Database.VirtualMachines.getAllVm() |> Array.map (fun x -> (x :> VirtualMachineData).Serialize())
  let vms2 = Database.VirtualMachines.getAllVmInPendingCreation() |> Array.map (fun x -> (x :> VirtualMachineData).Serialize())
  
  sw.WriteLine(true)
  sw.WriteLine( string(vms.Length + vms2.Length) )
  for vm in vms do sw.WriteLine(vm)
  for vm in vms2 do sw.WriteLine(vm)


// execute_remote_Octopus_function_withArrayReturn
let getVmByOwner (sr:StreamReader) (sw:StreamWriter) =
  let owner = sr.ReadLine()
  
  let vms = Database.VirtualMachines.getVmByOwner owner |> Array.map (fun x -> (x :> VirtualMachineData).Serialize())
  //let vms2 = Database.VirtualMachines.getVmInPendingCreationByOwner owner |> Array.map (fun x -> (x :> VirtualMachineData).Serialize())
  
  sw.WriteLine(true)
  //sw.WriteLine( string(vms.Length + vms2.Length) )
  sw.WriteLine( string(vms.Length) )
  for vm in vms do sw.WriteLine(vm)
  //for vm in vms2 do sw.WriteLine(vm)


(*
// execute_remote_Octopus_function_withReturn
let getTemplatesPath (sr:StreamReader) (sw:StreamWriter) =
  sw.WriteLine(true)
  sw.WriteLine(vmTemplatesPath)
*)

let private internal_getVmByOctopusGuid octopusVmGuid =
  try (Database.VirtualMachines.getVmByOctopusGuid octopusVmGuid) :> VirtualMachineData
  with _ -> Database.VirtualMachines.getAllVmInPendingCreation() |> Array.find (fun x -> x.OctopusGuid.Equals(octopusVmGuid)) :> VirtualMachineData
  

// execute_remote_Octopus_function_withReturn
let getVmByGuid (sr:StreamReader) (sw:StreamWriter) =
  let octopusVmGuid = new System.Guid( sr.ReadLine() )
  let vm = internal_getVmByOctopusGuid octopusVmGuid
  
  let ret = vm.Serialize()
  
  sw.WriteLine(true)
  sw.WriteLine(ret)


// execute_remote_Octopus_function_withReturn
let getActiveVmByGuid (sr:StreamReader) (sw:StreamWriter) =
  let octopusVmGuid = new System.Guid( sr.ReadLine() )
  let vm = Database.VirtualMachines.getVmByOctopusGuid octopusVmGuid
  
  let ret = vm.Serialize()
  
  sw.WriteLine(true)
  sw.WriteLine(ret)


// execute_remote_Octopus_function_withReturn
let getVmStateByGuid (sr:StreamReader) (sw:StreamWriter) =
  let octopusVmGuid = new System.Guid( sr.ReadLine() )
  
  let ret =
    try
      let vm = Database.VirtualMachines.getVmByOctopusGuid octopusVmGuid
      match (vm.HyperVHealthState,vm.OctopusState,vm.HyperVState) with
      | (_,OctopusVmState.ScheduledForCreation,_) -> OctopusVmState.ScheduledForCreation.ToString()
      | (_,OctopusVmState.ScheduledForConfiguring,_) -> OctopusVmState.Configuring.ToString()
      | (_,OctopusVmState.Configuring,_) -> OctopusVmState.Configuring.ToString()
      | (HyperVHealthState.OK,OctopusVmState.Normal,x) -> x.ToString()
      | (HyperVHealthState.OK,x,_) -> x.ToString()
      | (HyperVHealthState.CriticalFailure,_,_) -> HyperVHealthState.CriticalFailure.ToString()
      | (HyperVHealthState.MajorFailure,_,_) -> HyperVHealthState.MajorFailure.ToString()
      | (HyperVHealthState.Unreachable,_,_) -> HyperVHealthState.Unreachable.ToString()
      | (HyperVHealthState.HostUnreachable,_,_) -> HyperVHealthState.HostUnreachable.ToString()
      | (_,_,_) -> OctopusVmState.GenericError.ToString()
    with _ ->
      let vm = Database.VirtualMachines.getAllVmInPendingCreation() |> Array.find (fun x -> x.OctopusGuid.Equals(octopusVmGuid))
      vm.OctopusState.ToString()
  
  sw.WriteLine(true)
  sw.WriteLine(ret)


// execute_remote_Octopus_function_withReturn
(*
let getVmStateByGuid_RealTime (sr:StreamReader) (sw:StreamWriter) =
  let guid = new System.Guid( sr.ReadLine() )
  let vm = internal_getVmByGuid guid
  
  if ... then
    SchedulerCore.updateVmState vm  
  
  let ret = 
    if vm.OctopusState = OctopusVmState.Normal then vm.HyperVState.ToString()
    else vm.OctopusState.ToString()
  
  sw.WriteLine(true)
  sw.WriteLine(ret)
*)


// execute_remote_Octopus_function_withReturn
let getVmIPv4AddressByGuid (sr:StreamReader) (sw:StreamWriter) =
  let octopusVmGuid = new System.Guid( sr.ReadLine() )
  let vm = Database.VirtualMachines.getVmByOctopusGuid octopusVmGuid
  
  let ret =
      try
        // TODO: it exists integration services for Linux SUSE,
        // but I don't think that it has an rdp service installed...
        // so maybe the filed RDPAddressIPv4 results null.
        // I need to check that.
        let hfvm = HyperF.HyperF.get_VM (Some vm.CurrentServer) vm.HyperVGuid
        let kvp = HyperF.HyperF.get_VMIntegrationServicesInfo hfvm
        kvp.RDPAddressIPv4
      with _ ->
        try
          let ip = HyperF.HyperF.get_IpAddressByMac vm.MacAddress
          string ip
        with _ ->
          string vm.Ipv4Address
          
  if ret <> (string vm.Ipv4Address) then
    Database.VirtualMachines.updateVirtualMachineIpv4Address vm.OctopusGuid (System.Net.IPAddress.Parse(ret))

  sw.WriteLine(true)
  sw.WriteLine(ret)
  

// execute_remote_Octopus_function_withReturn
let getVmRDPAddressByGuid (sr:StreamReader) (sw:StreamWriter) =
  let octopusVmGuid = new System.Guid( sr.ReadLine() )
  let vm = Database.VirtualMachines.getVmByOctopusGuid octopusVmGuid
  
  let vm = HyperF.HyperF.get_VM (Some vm.CurrentServer) vm.HyperVGuid
  let kvp = HyperF.HyperF.get_VMIntegrationServicesInfo vm
  let ret = kvp.RDPAddressIPv4
  if ret = null then failwith "Address not found"

  sw.WriteLine(true)
  sw.WriteLine(ret)


// execute_remote_Octopus_function
let sendMessage (sr:StreamReader) (sw:StreamWriter) =
  let recipient = sr.ReadLine()
  let message = sr.ReadLine()
  
  Database.MessagesToUsers.insertNewMessage recipient message

  sw.WriteLine(true)


// execute_remote_Octopus_function_withArrayReturn
let getMessages (sr:StreamReader) (sw:StreamWriter) =
  let recipient = sr.ReadLine()
  
  let msgs = Database.MessagesToUsers.getMessages recipient |> Array.map (fun x -> x.Serialize())
  
  sw.WriteLine(true)
  sw.WriteLine(msgs.Length.ToString())
  for msg in msgs do sw.WriteLine(msg)


// execute_remote_Octopus_function_withArrayReturn
let getAllVmTemplates (sr:StreamReader) (sw:StreamWriter) =
  let templates = Database.VMTemplates.getVMTemplates() |> Array.map (fun x -> x.Serialize())

  sw.WriteLine(true)
  sw.WriteLine(templates.Length.ToString())
  for t in templates do sw.WriteLine(t)


// execute_remote_Octopus_function_withReturn
let getVmTemplatesById (sr:StreamReader) (sw:StreamWriter) =
  let templateId = Int32.Parse( sr.ReadLine() )

  let template = Database.VMTemplates.getVMTemplate templateId
  let ret = template.Serialize()

  sw.WriteLine(true)
  sw.WriteLine(ret)


// execute_remote_Octopus_function
let createNewVmTemplate (sr:StreamReader) (sw:StreamWriter) =
  let parentId = Int32.Parse( sr.ReadLine() )
  let name = sr.ReadLine()
  let description = sr.ReadLine()
  let cpuCountMin = UInt16.Parse( sr.ReadLine() )
  let cpuCountMax = UInt16.Parse( sr.ReadLine() )
  let memorySizeMin = Int32.Parse( sr.ReadLine() )
  let memorySizeMax = Int32.Parse( sr.ReadLine() )
  let nicSwitchName = sr.ReadLine()
  let vhdPath = sr.ReadLine()
  let isHpcNode = Boolean.Parse( sr.ReadLine() )
  let hpcScheduler = sr.ReadLine()
  
  Database.VMTemplates.insertNewVMTemplate name description cpuCountMin cpuCountMax memorySizeMin memorySizeMax nicSwitchName vhdPath parentId isHpcNode hpcScheduler |> ignore

  sw.WriteLine(true)


//-------------------------------------------
// These functions are temporary, only for debug purpose
//-------------------------------------------
  
let tempCreateDb (sr:StreamReader) (sw:StreamWriter) =
  Database.Core.createDatabaseFile ()
  sw.WriteLine(true)

let tempDeleteDb (sr:StreamReader) (sw:StreamWriter) =
  SchedulerCore.ServiceCoreMainLoop.StopWithJoin()
  System.Threading.Thread.Sleep(2000)       // 2 more seconds...
  Database.Core.deleteDatabaseFile ()
  sw.WriteLine(true)


//-----------------------------------------------------


// TODO: Delete this function
(*
let tempUpdateHyperVNodesFromXml () =

  let hypervServers =
    let xml = new System.Xml.XmlDocument()
    xml.Load(Database.serviceBinPath + @"\serviceConf.xml")
    let nodes = xml.SelectNodes("/OctopusConf/HyperVServers/Server")
    [| for n in nodes -> (n.Item("Host").InnerText.Trim().ToUpper(),
                          n.Item("SaveVmPath").InnerText.Trim()) |]
    
  for (name,path) in hypervServers do
    try
      Database.HyperVNodes.getHyperVNodeByName name |> ignore
    with _ ->
      let (isReachable, cpuCount, memorySize) =
        try let c = new Octopus.OctopusLib.HostNodeWindowsCounters(name)
            (true, (uint16 c.CpuCount), c.MemorySize)
        with _ -> (false, 0us, 0UL)
  
      let host = new HyperVNode(-1,name,"0.0.0.0",path,cpuCount,memorySize,isReachable,0.f,0L,System.DateTime.Now)
      try Database.HyperVNodes.insertHyperVNode host
      with _ -> ()
*)

//-----------------------------------------------------


let tempAllVm (sr:StreamReader) (sw:StreamWriter) =
  let vms = Database.VirtualMachines.getAllVm()
  
  sw.WriteLine(true)
  for vm in vms do sw.WriteLine( sprintf @"""%s"" owned by %s" vm.HostName vm.OwnerName )


//---

// execute_remote_Octopus_function_withArrayReturn
let tmpGetHyperVNodes (sr:StreamReader) (sw:StreamWriter) =
  let nodes = Database.HyperVNodes.getHyperVNodes() |> Array.map (fun x -> x.Serialize())
  
  sw.WriteLine(true)
  sw.WriteLine(nodes.Length.ToString())
  for node in nodes do sw.WriteLine(node)
  


//-----------------------------------------------------
(* For unknown reason, probably a Hyper-V bug, this code doesn't create
 * a valid export file (Hyper-V doesn't recognize that)
 * (of course, starting with a valid .exp file!)

      let path1 = exportPath + "\\Virtual Machines\\" + vm.Guid.ToString() + ".exp"
      use sr = new StreamReader(path1, System.Text.Encoding.Unicode)
      let xml = new System.Xml.XmlDocument()
      xml.Load(sr)
      sr.Close()
      let tmp = xml.SelectNodes("/DECLARATIONS/DECLGROUP/VALUE.OBJECT/INSTANCE[@CLASSNAME='Msvm_VirtualSystemExportSettingData']/PROPERTY[@NAME='CopyVmRuntimeInformation']/VALUE")
      tmp.[0].InnerText <- "TRUE"
      use sw = new StreamWriter(path1, false, System.Text.Encoding.Unicode)
      xml.Save(sw)
      sw.Flush()
      sw.Close()

 *)